Skip to content

Method: static {...}

1: /*
2: * Copyright © 2020-2023 Fachhochschule für die Wirtschaft (FHDW) Hannover
3: *
4: * This file is part of othello-core.
5: *
6: * Othello-core is free software: you can redistribute it and/or modify
7: * it under the terms of the GNU General Public License as published by
8: * the Free Software Foundation, either version 3 of the License, or
9: * (at your option) any later version.
10: *
11: * Othello-core is distributed in the hope that it will be useful,
12: * but WITHOUT ANY WARRANTY; without even the implied warranty of
13: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14: * GNU General Public License for more details.
15: *
16: * You should have received a copy of the GNU General Public License
17: * along with othello-core. If not, see <http://www.gnu.org/licenses/>.
18: */
19: package de.fhdw.gaming.othello.core.domain.impl;
20:
21: import java.util.Collections;
22: import java.util.LinkedHashSet;
23: import java.util.Objects;
24: import java.util.Set;
25:
26: import de.fhdw.gaming.core.domain.GameException;
27: import de.fhdw.gaming.othello.core.domain.OthelloBoard;
28: import de.fhdw.gaming.othello.core.domain.OthelloDirection;
29: import de.fhdw.gaming.othello.core.domain.OthelloField;
30: import de.fhdw.gaming.othello.core.domain.OthelloFieldState;
31: import de.fhdw.gaming.othello.core.domain.OthelloPosition;
32:
33: /**
34: * Implements {@link OthelloField}.
35: */
36: final class OthelloFieldImpl implements OthelloField {
37:
38: /**
39: * The board this field belongs to.
40: */
41: private final OthelloBoardImpl board;
42: /**
43: * The position at which this field is placed on the board.
44: */
45: private final OthelloPosition position;
46: /**
47: * The state of this field.
48: */
49: private OthelloFieldState state;
50:
51: /**
52: * Creates an Othello field.
53: *
54: * @param board The board this field belongs to.
55: * @param position The position at which this field is placed on the board.
56: * @param state The state of this field.
57: */
58: OthelloFieldImpl(final OthelloBoardImpl board, final OthelloPosition position, final OthelloFieldState state) {
59: this.board = Objects.requireNonNull(board, "board");
60: this.position = Objects.requireNonNull(position, "position");
61: this.state = state;
62: }
63:
64: @Override
65: public String toString() {
66: return String.format("OthelloField[position=%s, state=%s]", this.position, this.state);
67: }
68:
69: /**
70: * {@inheritDoc}
71: * <p>
72: * Does not compare the boards the fields belong to, respectively.
73: */
74: @Override
75: public boolean equals(final Object obj) {
76: if (obj instanceof OthelloFieldImpl) {
77: final OthelloFieldImpl other = (OthelloFieldImpl) obj;
78: return this.position.equals(other.position) && this.state.equals(other.state);
79: }
80: return false;
81: }
82:
83: @Override
84: public int hashCode() {
85: return Objects.hash(this.position, this.state);
86: }
87:
88: @Override
89: public OthelloBoard getBoard() {
90: return this.board;
91: }
92:
93: @Override
94: public OthelloPosition getPosition() {
95: return this.position;
96: }
97:
98: @Override
99: public OthelloFieldState getState() {
100: return this.state;
101: }
102:
103: /**
104: * Sets the current state of this field. Does not change the states of neighbour fields.
105: *
106: * @param newState The new state.
107: * @throws IllegalArgumentException if the new state is {@link OthelloFieldState#EMPTY} and different from the
108: * previous state.
109: */
110: void setState(final OthelloFieldState newState) throws IllegalArgumentException {
111: final OthelloFieldState oldState = this.state;
112: if (oldState.equals(newState)) {
113: // nothing to do
114: return;
115: }
116:
117: final boolean wasEmpty = oldState.equals(OthelloFieldState.EMPTY);
118: final boolean isEmpty = newState.equals(OthelloFieldState.EMPTY);
119:
120: if (!wasEmpty && isEmpty) {
121: throw new IllegalArgumentException(
122: String.format("The field at %s cannot become empty again.", this.position));
123: }
124:
125: this.state = newState;
126: this.board.fieldChangedState(this, oldState);
127: }
128:
129: @Override
130: public boolean hasNeighbour(final OthelloDirection direction) {
131: return this.board.hasFieldAt(direction.step(this.position));
132: }
133:
134: @Override
135: public OthelloFieldImpl getNeighbour(final OthelloDirection direction) {
136: if (!this.hasNeighbour(direction)) {
137: throw new IllegalArgumentException(String.format("No %s neighbour at %s.", direction, this.position));
138: }
139:
140: return this.board.getFieldAt(direction.step(this.position));
141: }
142:
143: @Override
144: public boolean isActive(final boolean placingBlackToken) {
145: if (!this.state.equals(OthelloFieldState.EMPTY)) {
146: return false;
147: }
148:
149: final OthelloFieldState delimiterState = placingBlackToken ? OthelloFieldState.BLACK : OthelloFieldState.WHITE;
150: for (final OthelloDirection direction : OthelloDirection.values()) {
151: if (!this.getLineOfTokens(direction, delimiterState).isEmpty()) {
152: return true;
153: }
154: }
155:
156: return false;
157: }
158:
159: @Override
160: public Set<OthelloFieldImpl> getLineOfTokens(final OthelloDirection direction,
161: final OthelloFieldState delimiterState) {
162:
163: final Set<OthelloFieldImpl> fields = new LinkedHashSet<>();
164: OthelloFieldImpl currentField = this;
165: while (currentField.hasNeighbour(direction)) {
166: currentField = currentField.getNeighbour(direction);
167: if (currentField.getState().equals(delimiterState)) {
168: // properly delimited line of tokens found
169: return fields;
170: } else if (currentField.getState().equals(OthelloFieldState.EMPTY)) {
171: // line of tokens delimited by empty field
172: return Collections.emptySet();
173: } else {
174: assert currentField.getState().equals(delimiterState.inverse());
175: fields.add(currentField);
176: }
177: }
178:
179: // line of tokens delimited by board
180: return Collections.emptySet();
181: }
182:
183: @Override
184: public void placeToken(final boolean blackToken) throws GameException {
185: final OthelloFieldState newState = blackToken ? OthelloFieldState.BLACK : OthelloFieldState.WHITE;
186:
187: // a new token can be placed only on empty fields
188: if (!this.state.equals(OthelloFieldState.EMPTY)) {
189: throw new GameException(
190: String.format(
191: "Placing a token being %s on the non-empty field at %s"
192: + " is not allowed according to the rules of the game.",
193: newState,
194: this.position));
195: }
196:
197: // a new token needs to cause at least one other token to be flipped
198: final Set<OthelloFieldImpl> fieldsToBeFlipped = new LinkedHashSet<>();
199: for (final OthelloDirection direction : OthelloDirection.values()) {
200: fieldsToBeFlipped.addAll(this.getLineOfTokens(direction, newState));
201: }
202: if (fieldsToBeFlipped.isEmpty()) {
203: throw new GameException(
204: String.format(
205: "Placing a token being %s on the field at %s"
206: + " is not allowed as no other token can be flipped.",
207: newState,
208: this.position));
209: }
210:
211: // placement is valid, change state
212: this.setState(newState);
213:
214: for (final OthelloFieldImpl fieldToBeFlipped : fieldsToBeFlipped) {
215: assert !fieldToBeFlipped.getState().equals(OthelloFieldState.EMPTY);
216: fieldToBeFlipped.setState(newState);
217: }
218: }
219: }